﻿using System;
using System.Collections.Generic;
using System.IO;

namespace maranov.SymlinkManager.FilesystemLinks
{
    /// <summary>
    /// Symbolic link.
    /// </summary>
    public partial class SymbolicLink : Link, ISymbolicLink
    {
        /// <summary>
        /// Relative target name if stored in relative format.
        /// Otherwise null.
        /// </summary>
        private string RelativeTarget;

        /// <summary>
        /// Symbolic link.
        /// </summary>
        /// <param name="name">Symbolic link's name.</param>
        /// <param name="target">Symbolic link's target.</param>
        /// <param name="type">Determines the type of path saved in link's location.</param>
        public SymbolicLink(string name, string target, SymbolicLinkType type = SymbolicLinkType.Absolute) : 
            base(
                // Path is always stored in absolute format.
                Path.GetFullPath(name),
                (type == SymbolicLinkType.Relative) ?
                    // Parsing relative path to the link's name.
                    Path.GetFullPath(Path.GetDirectoryName(name) + target)
                    :
                    Path.GetFullPath(target)
            )
        {
            PathType = type;

            IsRelative = (PathType == SymbolicLinkType.Relative);
            if (IsRelative)
                RelativeTarget = target;
        }

        public bool IsRelative
        {
            get;
            private set;
        }

        public SymbolicLinkType PathType
        {
            get;
            private set;
        }

        public override void Create()
        {
            if (!Exists)
            {
                // Set the directory flag for a symlink according to the target type.
                SymbolicLinkFlag flag;
                if ((File.GetAttributes(Target.Name) & FileAttributes.Directory) == FileAttributes.Directory)
                    flag = SymbolicLinkFlag.Directory;
                else
                    flag = SymbolicLinkFlag.File;

                // Create symlink
                if (!CreateSymbolicLink(Name.Name, IsRelative ? RelativeTarget : Target.Name, flag))
                    throw new LinkCreationException("Symbolic link creation failed.", GetLastError());
            }
            else
            {
                if (State.HasFlag(LinkState.ConnectionExists))
                    throw new LinkCreationException("Symbolic link already exists in the link's location.");
                else
                    throw new LinkCreationException("Some file already exists in the link's location.");
            }
        }

        public override void Remove(bool force = false)
        {
            if (force ||
                State.HasFlag(LinkState.ConnectedLink))
            {
                RemoveSymbolicLink(Name.Name);
            }
            else
            {
                if (State.HasFlag(LinkState.LinkExists))
                    throw new LinkRemovalException("Symbolic link does not exist in specified location.");
                else
                    throw new LinkRemovalException("Target name is not the specified symbolic link and cannot be removed as one.");
            }
        }

        protected override bool ValidateLink()
        {
            return (State.HasFlag(LinkState.ConnectedLink));
        }

        protected override bool ValidateLinkConnection()
        {
            var reparsePoint = new ReparsePoint(Name.Name);

            if (reparsePoint.Tag != ReparsePoint.TagType.SymbolicLink)
                return false;

            if (IsRelative)
            {
                if (reparsePoint.Target != RelativeTarget)
                    return false;
            }
            else
            {
                if (reparsePoint.Target != Target.Name)
                    return false;
            }

            return true;
        }
    }
}
